/*
 * Copyright 2006 The Android Open Source Project
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkScalerContext_DEFINED
#define SkScalerContext_DEFINED

#include <memory>

#include "include/core/SkFont.h"
#include "include/core/SkFontTypes.h"
#include "include/core/SkMaskFilter.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPaint.h"
#include "include/core/SkTypeface.h"
#include "include/private/base/SkMacros.h"
#include "src/core/SkGlyph.h"
#include "src/core/SkMask.h"
#include "src/core/SkMaskGamma.h"
#include "src/core/SkSurfacePriv.h"
#include "src/core/SkWriteBuffer.h"

class SkAutoDescriptor;
class SkDescriptor;
class SkMaskFilter;
class SkPathEffect;
class SkScalerContext;
class SkScalerContext_DW;

enum class SkScalerContextFlags : uint32_t {
    kNone = 0,
    kFakeGamma = 1 << 0,
    kBoostContrast = 1 << 1,
    kFakeGammaAndBoostContrast = kFakeGamma | kBoostContrast,
};
SK_MAKE_BITFIELD_OPS(SkScalerContextFlags)

/*
 * To allow this to be forward-declared, it must be its own typename, rather
 * than a nested struct inside SkScalerContext (where it started).
 *
 * SkScalerContextRec must be dense, and all bytes must be set to a know quantity because this
 * structure is used to calculate a checksum.
 */
SK_BEGIN_REQUIRE_DENSE
struct SkScalerContextRec {
    SkTypefaceID fTypefaceID;
    SkScalar fTextSize, fPreScaleX, fPreSkewX;
    SkScalar fPost2x2[2][2];
    SkScalar fFrameWidth, fMiterLimit;

    // This will be set if to the paint's foreground color if
    // kNeedsForegroundColor is set, which will usually be the case for COLRv0 and
    // COLRv1 fonts.
    uint32_t fForegroundColor{ SK_ColorBLACK };

private:
    // These describe the parameters to create (uniquely identify) the pre-blend.
    uint32_t fLumBits;
    uint8_t fDeviceGamma; // 2.6, (0.0, 4.0) gamma, 0.0 for sRGB
    uint8_t fPaintGamma;  // 2.6, (0.0, 4.0) gamma, 0.0 for sRGB
    uint8_t fContrast;    // 0.8+1, [0.0, 1.0] artificial contrast
    const uint8_t fReservedAlign{ 0 };

public:
    SkScalar getDeviceGamma() const
    {
        return SkIntToScalar(fDeviceGamma) / (1 << 6);
    }
    void setDeviceGamma(SkScalar dg)
    {
        SkASSERT(0 <= dg && dg < SkIntToScalar(4));
        fDeviceGamma = SkScalarFloorToInt(dg * (1 << 6));
    }

    SkScalar getPaintGamma() const
    {
        return SkIntToScalar(fPaintGamma) / (1 << 6);
    }
    void setPaintGamma(SkScalar pg)
    {
        SkASSERT(0 <= pg && pg < SkIntToScalar(4));
        fPaintGamma = SkScalarFloorToInt(pg * (1 << 6));
    }

    SkScalar getContrast() const
    {
        sk_ignore_unused_variable(fReservedAlign);
        return SkIntToScalar(fContrast) / ((1 << 8) - 1);
    }
    void setContrast(SkScalar c)
    {
        SkASSERT(0 <= c && c <= SK_Scalar1);
        fContrast = SkScalarRoundToInt(c * ((1 << 8) - 1));
    }

    /* *
     * Causes the luminance color to be ignored, and the paint and device
     * gamma to be effectively 1.0
     */
    void ignoreGamma()
    {
        setLuminanceColor(SK_ColorTRANSPARENT);
        setPaintGamma(SK_Scalar1);
        setDeviceGamma(SK_Scalar1);
    }

    /* *
     * Causes the luminance color and contrast to be ignored, and the
     * paint and device gamma to be effectively 1.0.
     */
    void ignorePreBlend()
    {
        ignoreGamma();
        setContrast(0);
    }

    SkMask::Format fMaskFormat;

private:
    uint8_t fStrokeJoin : 4;
    uint8_t fStrokeCap : 4;

public:
    uint16_t fFlags;

    // Warning: when adding members note that the size of this structure
    // must be a multiple of 4. SkDescriptor requires that its arguments be
    // multiples of four and this structure is put in an SkDescriptor in
    // SkPaint::MakeRecAndEffects.

    SkString dump() const
    {
        SkString msg;
        msg.appendf("    Rec\n");
        msg.appendf("      textsize %a prescale %a preskew %a post [%a %a %a %a]\n", fTextSize, fPreScaleX, fPreSkewX,
            fPost2x2[0][0], fPost2x2[0][1], fPost2x2[1][0], fPost2x2[1][1]);
        msg.appendf("      frame %g miter %g format %d join %d cap %d flags %#hx\n", fFrameWidth, fMiterLimit,
            fMaskFormat, fStrokeJoin, fStrokeCap, fFlags);
        msg.appendf("      lum bits %x, device gamma %d, paint gamma %d contrast %d\n", fLumBits, fDeviceGamma,
            fPaintGamma, fContrast);
        msg.appendf("      foreground color %x\n", fForegroundColor);
        return msg;
    }

    void getMatrixFrom2x2(SkMatrix *) const;
    void getLocalMatrix(SkMatrix *) const;
    void getSingleMatrix(SkMatrix *) const;

    /* * The kind of scale which will be applied by the underlying port (pre-matrix). */
    enum class PreMatrixScale {
        kFull,           // The underlying port can apply both x and y scale.
        kVertical,       // The underlying port can only apply a y scale.
        kVerticalInteger // The underlying port can only apply an integer y scale.
    };
    /* *
     * Compute useful matrices for use with sizing in underlying libraries.
     *
     * There are two kinds of text size, a 'requested/logical size' which is like asking for size
     * '12' and a 'real' size which is the size after the matrix is applied. The matrices produced
     * by this method are based on the 'real' size. This method effectively finds the total device
     * matrix and decomposes it in various ways.
     *
     * The most useful decomposition is into 'scale' and 'remaining'. The 'scale' is applied first
     * and then the 'remaining' to fully apply the total matrix. This decomposition is useful when
     * the text size ('scale') may have meaning apart from the total matrix. This is true when
     * hinting, and sometimes true for other properties as well.
     *
     * The second (optional) decomposition is of 'remaining' into a non-rotational part
     * 'remainingWithoutRotation' and a rotational part 'remainingRotation'. The 'scale' is applied
     * first, then 'remainingWithoutRotation', then 'remainingRotation' to fully apply the total
     * matrix. This decomposition is helpful when only horizontal metrics can be trusted, so the
     * 'scale' and 'remainingWithoutRotation' will be handled by the underlying library, but
     * the final rotation 'remainingRotation' will be handled manually.
     *
     * The 'total' matrix is also (optionally) available. This is useful in cases where the
     * underlying library will not be used, often when working directly with font data.
     *
     * The parameters 'scale' and 'remaining' are required, the other pointers may be nullptr.
     *
     * @param preMatrixScale the kind of scale to extract from the total matrix.
     * @param scale the scale extracted from the total matrix (both values positive).
     * @param remaining apply after scale to apply the total matrix.
     * @param remainingWithoutRotation apply after scale to apply the total matrix sans rotation.
     * @param remainingRotation apply after remainingWithoutRotation to apply the total matrix.
     * @param total the total matrix.
     * @return false if the matrix was singular. The output will be valid but not invertible.
     */
    bool computeMatrices(PreMatrixScale preMatrixScale, SkVector *scale, SkMatrix *remaining,
        SkMatrix *remainingWithoutRotation = nullptr, SkMatrix *remainingRotation = nullptr, SkMatrix *total = nullptr);

    SkAxisAlignment computeAxisAlignmentForHText() const;

    inline SkFontHinting getHinting() const;
    inline void setHinting(SkFontHinting);

    SkMask::Format getFormat() const
    {
        return fMaskFormat;
    }

    SkColor getLuminanceColor() const
    {
        return fLumBits;
    }

    // setLuminanceColor forces the alpha to be 0xFF because the blitter that draws the glyph
    // will apply the alpha from the paint. Don't apply the alpha twice.
    void setLuminanceColor(SkColor c);

private:
    // TODO: remove
    friend class SkScalerContext;
};
SK_END_REQUIRE_DENSE

// TODO: rename SkScalerContextEffects -> SkStrikeEffects
struct SkScalerContextEffects {
    SkScalerContextEffects() : fPathEffect(nullptr), fMaskFilter(nullptr) {}
    SkScalerContextEffects(SkPathEffect *pe, SkMaskFilter *mf) : fPathEffect(pe), fMaskFilter(mf) {}
    explicit SkScalerContextEffects(const SkPaint &paint)
        : fPathEffect(paint.getPathEffect()), fMaskFilter(paint.getMaskFilter())
    {}

    SkPathEffect *fPathEffect;
    SkMaskFilter *fMaskFilter;
};

// The following typedef hides from the rest of the implementation the number of
// most significant bits to consider when creating mask gamma tables. Two bits
// per channel was chosen as a balance between fidelity (more bits) and cache
// sizes (fewer bits). Three bits per channel was chosen when #303942; (used by
// the Chrome UI) turned out too green.
typedef SkTMaskGamma<3, 3, 3> SkMaskGamma;

class SkScalerContext {
public:
    enum Flags {
        kFrameAndFill_Flag = 0x0001,
        kUnused = 0x0002,
        kEmbeddedBitmapText_Flag = 0x0004,
        kEmbolden_Flag = 0x0008,
        kSubpixelPositioning_Flag = 0x0010,
        kForceAutohinting_Flag = 0x0020, // Use auto instead of bytcode hinting if hinting.

        // together, these two flags resulting in a two bit value which matches
        // up with the SkPaint::Hinting enum.
        kHinting_Shift = 7, // to shift into the other flags above
        kHintingBit1_Flag = 0x0080,
        kHintingBit2_Flag = 0x0100,

        // Pixel geometry information.
        // only meaningful if fMaskFormat is kLCD16
        kLCD_Vertical_Flag = 0x0200, // else Horizontal
        kLCD_BGROrder_Flag = 0x0400, // else RGB order

        // Generate A8 from LCD source (for GDI and CoreGraphics).
        // only meaningful if fMaskFormat is kA8
        kGenA8FromLCD_Flag = 0x0800, // could be 0x200 (bit meaning dependent on fMaskFormat)
        kLinearMetrics_Flag = 0x1000,
        kBaselineSnap_Flag = 0x2000,

        kNeedsForegroundColor_Flag = 0x4000,
    };

    // computed values
    enum {
        kHinting_Mask = kHintingBit1_Flag | kHintingBit2_Flag,
    };

    SkScalerContext(sk_sp<SkTypeface>, const SkScalerContextEffects &, const SkDescriptor *);
    virtual ~SkScalerContext();

    SkTypeface *getTypeface() const
    {
        return fTypeface.get();
    }

    SkMask::Format getMaskFormat() const
    {
        return fRec.fMaskFormat;
    }

    bool isSubpixel() const
    {
        return SkToBool(fRec.fFlags & kSubpixelPositioning_Flag);
    }

    bool isLinearMetrics() const
    {
        return SkToBool(fRec.fFlags & kLinearMetrics_Flag);
    }

    // DEPRECATED
    bool isVertical() const
    {
        return false;
    }

    SkGlyph makeGlyph(SkPackedGlyphID, SkArenaAlloc *);
    void getImage(const SkGlyph &);
    void getPath(SkGlyph &, SkArenaAlloc *);
    sk_sp<SkDrawable> getDrawable(SkGlyph &);
    void getFontMetrics(SkFontMetrics *);

    /* * Return the size in bytes of the associated gamma lookup table
     */
    static size_t GetGammaLUTSize(SkScalar contrast, SkScalar paintGamma, SkScalar deviceGamma, int *width,
        int *height);

    /* * Get the associated gamma lookup table. The 'data' pointer must point to pre-allocated
     * memory, with size in bytes greater than or equal to the return value of getGammaLUTSize().
     *
     * If the lookup table hasn't been initialized (e.g., it's linear), this will return false.
     */
    static bool GetGammaLUTData(SkScalar contrast, SkScalar paintGamma, SkScalar deviceGamma, uint8_t *data);

    static void MakeRecAndEffects(const SkFont &font, const SkPaint &paint, const SkSurfaceProps &surfaceProps,
        SkScalerContextFlags scalerContextFlags, const SkMatrix &deviceMatrix, SkScalerContextRec *rec,
        SkScalerContextEffects *effects);

    // If we are creating rec and effects from a font only, then there is no device around either.
    static void MakeRecAndEffectsFromFont(const SkFont &font, SkScalerContextRec *rec, SkScalerContextEffects *effects)
    {
        SkPaint paint;
        return MakeRecAndEffects(font, paint, SkSurfaceProps(), SkScalerContextFlags::kNone, SkMatrix::I(), rec,
            effects);
    }

    static std::unique_ptr<SkScalerContext> MakeEmpty(sk_sp<SkTypeface> typeface, const SkScalerContextEffects &effects,
        const SkDescriptor *desc);

    static SkDescriptor *AutoDescriptorGivenRecAndEffects(const SkScalerContextRec &rec,
        const SkScalerContextEffects &effects, SkAutoDescriptor *ad);

    static std::unique_ptr<SkDescriptor> DescriptorGivenRecAndEffects(const SkScalerContextRec &rec,
        const SkScalerContextEffects &effects);

    static void DescriptorBufferGiveRec(const SkScalerContextRec &rec, void *buffer);
    static bool CheckBufferSizeForRec(const SkScalerContextRec &rec, const SkScalerContextEffects &effects,
        size_t size);

    static SkMaskGamma::PreBlend GetMaskPreBlend(const SkScalerContextRec &rec);

    const SkScalerContextRec &getRec() const
    {
        return fRec;
    }

    SkScalerContextEffects getEffects() const
    {
        return { fPathEffect.get(), fMaskFilter.get() };
    }

    /* *
     * Return the axis (if any) that the baseline for horizontal text should land on.
     * As an example, the identity matrix will return SkAxisAlignment::kX.
     */
    SkAxisAlignment computeAxisAlignmentForHText() const;

    static SkDescriptor *CreateDescriptorAndEffectsUsingPaint(const SkFont &, const SkPaint &, const SkSurfaceProps &,
        SkScalerContextFlags scalerContextFlags, const SkMatrix &deviceMatrix, SkAutoDescriptor *ad,
        SkScalerContextEffects *effects);

protected:
    SkScalerContextRec fRec;

    struct GlyphMetrics {
        SkVector advance;
        SkRect bounds;
        SkMask::Format maskFormat;
        uint16_t extraBits;
        bool neverRequestPath;
        bool computeFromPath;

        GlyphMetrics(SkMask::Format format)
            : advance{ 0, 0 },
              bounds{ 0, 0, 0, 0 },
              maskFormat(format),
              extraBits(0),
              neverRequestPath(false),
              computeFromPath(false)
        {}
    };

    virtual GlyphMetrics generateMetrics(const SkGlyph &, SkArenaAlloc *) = 0;

    static void GenerateMetricsFromPath(SkGlyph *glyph, const SkPath &path, SkMask::Format format, bool verticalLCD,
        bool a8FromLCD, bool hairline);

    static void SaturateGlyphBounds(SkGlyph *glyph, SkRect &&);
    static void SaturateGlyphBounds(SkGlyph *glyph, SkIRect const &);

    /* * Generates the contents of glyph.fImage.
     * When called, glyph.fImage will be pointing to a pre-allocated,
     * uninitialized region of memory of size glyph.imageSize().
     * This method may not change glyph.fMaskFormat.
     *
     * Because glyph.imageSize() will determine the size of fImage,
     * generateMetrics will be called before generateImage.
     */
    virtual void generateImage(const SkGlyph &glyph, void *imageBuffer) = 0;
    static void GenerateImageFromPath(SkMaskBuilder &dst, const SkPath &path, const SkMaskGamma::PreBlend &maskPreBlend,
        bool doBGR, bool verticalLCD, bool a8FromLCD, bool hairline);

    /* * Sets the passed path to the glyph outline.
     * If this cannot be done the path is set to empty;
     * Does not apply subpixel positioning to the path.
     * @return false if this glyph does not have any path.
     */
    [[nodiscard]] virtual bool generatePath(const SkGlyph &, SkPath *) = 0;

    /* * Returns the drawable for the glyph (if any).
     *
     * The generated drawable will be lifetime scoped to the lifetime of this scaler context.
     * This means the drawable may refer to the scaler context and associated font data.
     *
     * The drawable does not need to be flattenable (e.g. implement getFactory and getTypeName).
     * Any necessary serialization will be done with makePictureSnapshot.
     */
    virtual sk_sp<SkDrawable> generateDrawable(const SkGlyph &); // TODO: = 0

    /* * Retrieves font metrics. */
    virtual void generateFontMetrics(SkFontMetrics *) = 0;

    void forceGenerateImageFromPath()
    {
        fGenerateImageFromPath = true;
    }
    void forceOffGenerateImageFromPath()
    {
        fGenerateImageFromPath = false;
    }

private:
    friend class PathText;            // For debug purposes
    friend class PathTextBench;       // For debug purposes
    friend class RandomScalerContext; // For debug purposes

    static SkScalerContextRec PreprocessRec(const SkTypeface &, const SkScalerContextEffects &, const SkDescriptor &);

    // never null
    sk_sp<SkTypeface> fTypeface;

    // optional objects, which may be null
    sk_sp<SkPathEffect> fPathEffect;
    sk_sp<SkMaskFilter> fMaskFilter;

    // if this is set, we draw the image from a path, rather than
    // calling generateImage.
    bool fGenerateImageFromPath;

    void internalGetPath(SkGlyph &, SkArenaAlloc *);
    SkGlyph internalMakeGlyph(SkPackedGlyphID, SkMask::Format, SkArenaAlloc *);

protected:
    // SkMaskGamma::PreBlend converts linear masks to gamma correcting masks.
    // Visible to subclasses so that generateImage can apply the pre-blend directly.
    const SkMaskGamma::PreBlend fPreBlend;
};

#define kRec_SkDescriptorTag SkSetFourByteTag('s', 'r', 'e', 'c')
#define kEffects_SkDescriptorTag SkSetFourByteTag('e', 'f', 'c', 't')

// /////////////////////////////////////////////////////////////////////////////

SkFontHinting SkScalerContextRec::getHinting() const
{
    unsigned hint = (fFlags & SkScalerContext::kHinting_Mask) >> SkScalerContext::kHinting_Shift;
    return static_cast<SkFontHinting>(hint);
}

void SkScalerContextRec::setHinting(SkFontHinting hinting)
{
    fFlags = (fFlags & ~SkScalerContext::kHinting_Mask) |
        (static_cast<unsigned>(hinting) << SkScalerContext::kHinting_Shift);
}


#endif
